package org.xx.armory.netty;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.util.AsciiString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xx.armory.commons.CalledBy;
import org.xx.armory.mbeans.RpcHandlerProxy;
import org.xx.armory.rpc.RpcHandler;
import org.xx.armory.rpc.routing.Route;
import org.xx.armory.rpc.routing.RouteTable;
import org.xx.armory.rpc.routing.XmlRouteFileParser;
import org.xx.armory.service.ServiceContext;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;

import static io.netty.buffer.Unpooled.copiedBuffer;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpUtil.isKeepAlive;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.commons.io.FileUtils.openInputStream;
import static org.apache.commons.lang3.StringUtils.trimToEmpty;
import static org.xx.armory.commons.MBeanUtils.registerMBean;
import static org.xx.armory.commons.MBeanUtils.unregisterMBean;
import static org.xx.armory.commons.Validators.notBlank;
import static org.xx.armory.commons.Validators.notNull;
import static org.xx.armory.mbeans.ApplicationProxy.JMX_DOMAIN;

/**
 * {@literal netty}的响应处理器。
 *
 * @author Haart
 */
@Sharable
public final class CoreHandler
        extends SimpleChannelInboundHandler<FullHttpRequest>
        implements ClosableChannelInboundHandler {
    private static final AsciiString CONTENT_LENGTH = AsciiString.of("Content-Length");
    private final Logger logger = LoggerFactory.getLogger(CoreHandler.class);
    private final RpcHandler handler;

    /**
     * 构造核心处理器对象实例。
     *
     * @param configurationRootUrl
     *         配置文件所在的目录。
     * @throws IllegalArgumentException
     *         如果参数{@code configurationRootUrl}是{@code null}或者只包含空白字符。
     * @throws Exception
     *         如果创建核心处理器出错。
     */
    public CoreHandler(
            String configurationRootUrl
    )
            throws Exception {
        configurationRootUrl = notBlank(configurationRootUrl, "configurationRootUrl").trim();

        final RouteTable routeTable = loadRouteTable(configurationRootUrl);
        logger.debug("{} route(s) loaded", routeTable.size());

        final RpcHandler rpcHandler = new RpcHandler();
        // TODO: 从Configuration中获取。
        rpcHandler.setParallelism(0);
        rpcHandler.setRouteTable(routeTable);
        rpcHandler.initialize();

        registerMBean(new RpcHandlerProxy(rpcHandler), JMX_DOMAIN, getClass().getSimpleName(), "");

        this.handler = rpcHandler;
    }

    /**
     * 向客户端发出指定的错误响应。
     *
     * @param ctx
     *         处理器上下文。
     * @param status
     *         错误状态码。
     * @param cause
     *         导致错误响应的原因。
     * @return ChannelFuture。
     * @throws NullPointerException
     *         如果参数 {@code ctx} 或者 {@code status} 是 {@code null} 。
     */
    private static ChannelFuture sendError(
            ChannelHandlerContext ctx,
            HttpResponseStatus status,
            String cause
    ) {
        notNull(ctx, "ctx cannot be null");
        notNull(status, "status cannot be null");
        cause = trimToEmpty(cause);

        final ByteBuf buffer = copiedBuffer(cause + "\n", UTF_8);
        final DefaultFullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, buffer);
        response.headers().set(CONTENT_LENGTH, buffer.readableBytes());
        return ctx.pipeline().writeAndFlush(response);
    }

    /**
     * 向客户端发出{@literal 400 Bad request}响应。
     *
     * @param ctx
     *         处理器上下文。
     * @param cause
     *         不接受请求的原因。
     * @return ChannelFuture。
     * @throws NullPointerException
     *         如果参数 {@code ctx} 是 {@code null} 。
     */
    private static ChannelFuture send400BadRequest(
            ChannelHandlerContext ctx,
            String cause
    ) {
        return sendError(ctx, BAD_REQUEST, cause);
    }

    /**
     * 加载路由表。
     *
     * @param configurationRootUrl
     *         配置文件所在的路径。
     * @return 已加载的路由表，如果无法加载路由表则返回不包含任何路由的路由表。
     * @throws IOException
     *         如果读取路由表文件失败。
     */
    private RouteTable loadRouteTable(
            String configurationRootUrl
    )
            throws IOException {
        try (final InputStream stream = getClass().getResourceAsStream("/armory-routes.xml")) {
            final RouteTable routeTable = new RouteTable();

            final Collection<Route> routes1 = new ArrayList<>();

            new XmlRouteFileParser().parse(stream, routes1);

            routeTable.addAll(routes1);

            return routeTable;
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @CalledBy("netty")
    protected void channelRead0(
            ChannelHandlerContext ctx,
            FullHttpRequest httpRequest
    )
            throws Exception {
        final DecoderResult result = httpRequest.decoderResult();
        if (result != null) {
            if (result.isSuccess()) {
                final FullHttpResponse httpResponse = new DefaultFullHttpResponse(HTTP_1_1, OK);

                final ChannelRpcRequest request = new ChannelRpcRequest(httpRequest);
                final ChannelRpcResponse response = new ChannelRpcResponse(httpResponse, ctx, isKeepAlive(httpRequest));
                final ServiceContext serviceContext = new ServiceContext();

                serviceContext.initialize();

                handler.handle(request, response, serviceContext)
                       .whenComplete((dummy, ex) -> {
                           // 请求处理完成后，依次关闭上下文，请求和响应。
                           try {
                               serviceContext.close();
                           } catch (Exception ex0) {
                               logger.warn("cannot close service context", ex0);
                           }

                           try {
                               request.close();
                           } catch (Exception ex0) {
                               logger.warn("cannot close request", ex0);
                           }

                           try {
                               response.close();
                           } catch (Exception ex0) {
                               logger.warn("cannot close response", ex0);
                           }
                       });
            } else {
                Throwable cause = result.cause();
                if (cause != null) {
                    logger.error("cannot decode request", cause);
                }

                send400BadRequest(ctx, "cannot decode request");
            }
        } else {
            send400BadRequest(ctx, "cannot decode request");
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @CalledBy("netty")
    public void exceptionCaught(
            ChannelHandlerContext ctx,
            Throwable cause
    )
            throws Exception {
        super.exceptionCaught(ctx, cause);

        logger.error("uncaught exception", cause);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void close()
            throws Exception {
        unregisterMBean(JMX_DOMAIN, getClass().getSimpleName(), "");
    }
}
